#include "Stdafx.h"

#include "DbReader.h"
#include "Compress.h"
#include "Index.h"

namespace EndGameDB
{

// compress database dir
wchar_t szReaderDatabaseDir[PATH_MAX];

// subdatabase record
cprsubdb cprsubdatabase[MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][9][9][2]; // db subslice info

// dtc databases
dtcdatabase dtcsubdatabase[MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][MAXPIECE+1][9][9][2];

// the cache
unsigned char *CacheBaseAddress; // allocate cache memory to this pointer
unsigned char **BlockPointer;	// pointers to the memory address of block #i, using unique block id i.
										// watch out for 32/64-bit compatibility problem
unsigned char *DTCCacheBaseAddress;
unsigned char **DTCBlockPointer;

int CacheSize; // the size of cache

bi* BlockInfo;
bi* DTCBlockInfo;

int64 Head,Tail; // array index of head and tail of the linked list.
int64 DTCHead, DTCTail;

// array for decoding
int RunLength[256]; // holds the run length for every byte
int Value[256];		// holds the value for every byte

// number of db buffers
int maxblocknum;
// max number of idx buffers
int maxidx;
int maxpieces;
int maxpiece;

FILE *dbfp[MAXFP]; // file pointers to db2...dbn - always open.
FILE *dtcdbfp[MAXFP]; // file pointers to db2...dbn - always open.


void SetDatabaseReaderDirectory(const wchar_t* lpDir)
{
	wcscpy_s(szReaderDatabaseDir, PATH_MAX, lpDir);
}

void SetReaderDatabaseName(wchar_t *dbname, int numPieces, bool index, bool dtc)
{
	wcscpy_s(dbname, PATH_MAX, szReaderDatabaseDir);
	int len = (int)wcslen(dbname);
	if(dbname[len - 1] != L'\\')
	{
		wcscat_s(dbname, PATH_MAX, L"\\");
	}

	wchar_t szFileName[PATH_MAX];

	if(dtc == true)
	{
		if(index == true)
			swprintf_s(szFileName, PATH_MAX,L"db%i.dtc.idx", numPieces);
		else
			swprintf_s(szFileName, PATH_MAX, L"db%i.dtc.cpr", numPieces);
	}
	else
	{
		if(index == true)
			swprintf_s(szFileName, PATH_MAX,L"db%i.idx", numPieces);
		else
			swprintf_s(szFileName, PATH_MAX, L"db%i.cpr", numPieces);
	}

	wcscat_s(dbname, PATH_MAX, szFileName);
}


int ParseDtcIndexFile(wchar_t* idxFilename, int blockoffset, int fpcount)
{
	FILE *fp;
	char c,colorchar;
	int bm,bk,wm,wk,bmrank,wmrank,color;
	int startbyte;
	int idx[MAXIDX8];
	int num=0;
	int firstblock = 0;
	int stat,stat0;
	dtcdb *dbpointer;

	// open the file
	_wfopen_s(&fp, idxFilename, L"r");
	if(fp == NULL)
		return -1;

	while (1) 
	{
		// At this point it has to be a BASE line or else end of file. 
		stat = fscanf_s(fp, " BASE%i,%i,%i,%i,%i,%i,%c:",
				&wm, &wk, &bm, &bk, &wmrank, &bmrank, &colorchar);
		
		// Done if end-of-file reached. 
		if (stat <= 0)
			break;
		else if (stat < 7) 
		{
			return -2;
		}
		
		// Decode the color. 
		if (colorchar == 'b')
			color = BLACK;
		else
			color = WHITE;


		// Get the rest of the line.  It could be a n/n, or it could just
		// be a single character that is '+', '=', or '-'.
		stat = fgetc(fp);
		if (isdigit(stat)) 
		{
			ungetc(stat, fp);
			stat0 = fscanf_s(fp, "%d/%d", &firstblock, &startbyte);
			if (stat0 < 2) 
			{
				stat = fscanf_s(fp, "%c", &c);
				if (stat < 1) 
				{
					return -2;
				}
			}

			dbpointer = &dtcsubdatabase[wm][wk][bm][bk][wmrank][bmrank][color];
			dbpointer->firstblock = firstblock;		// which block in db. 
			dbpointer->blockoffset = blockoffset;	// which block overall. 
			dbpointer->ispresent = 1;				// watch out: this should be initialized to 0 for all!
			dbpointer->startbyte = startbyte;
			dbpointer->fp = fpcount;

			// now, we got the first line, maybe there are more... 
			// count the number of blocks in the db.
			num = 1;
			idx[0] = 0;		// first block is index 0. 
			while (fscanf_s(fp, "%d", idx + num) == 1) 
			{
				num++;

				// Check for too many indices.
				//if (num == MAXIDX8) 
				//{
				//	return -2;
				//}
			} // while

			// We stopped reading numbers. 
			dbpointer->numberofblocks = num;
			dbpointer->idx = new int[num];

			memcpy(dbpointer->idx, idx, num * sizeof(int));
		}
	}

	fclose(fp);

	return num+firstblock;
}


int ParseIndexFile(wchar_t* idxFilename, int blockoffset, int fpcount)
{
	FILE *fp;
	char c,colorchar;
	int bm,bk,wm,wk,bmrank,wmrank,color;
	int singlevalue,startbyte;
	int idx[MAXIDX8];
	int num=0;
	int firstblock = 0;
	int stat,stat0;
	cprsubdb *dbpointer;

	// open the file
	_wfopen_s(&fp, idxFilename, L"r");
	if(fp == NULL)
		return -1;

	while (1) 
	{
		// At this point it has to be a BASE line or else end of file. 
		stat = fscanf_s(fp, " BASE%i,%i,%i,%i,%i,%i,%c:",
				&wm, &wk, &bm, &bk, &wmrank, &bmrank, &colorchar);
		
		// Done if end-of-file reached. 
		if (stat <= 0)
			break;
		else if (stat < 7) 
		{
			return -2;
		}
		
		// Decode the color. 
		if (colorchar == 'b')
			color = BLACK;
		else
			color = WHITE;


		// Get the rest of the line.  It could be a n/n, or it could just
		// be a single character that is '+', '=', or '-'.
		stat = fgetc(fp);
		if (isdigit(stat)) 
		{
			ungetc(stat, fp);
			stat0 = fscanf_s(fp, "%d/%d", &firstblock, &startbyte);
			if (stat0 < 2) 
			{
				stat = fscanf_s(fp, "%c", &c);
				if (stat < 1) 
				{
					return -2;
				}
			}

			dbpointer = &cprsubdatabase[wm][wk][bm][bk][wmrank][bmrank][color];
			dbpointer->firstblock = firstblock;		// which block in db. 
			dbpointer->blockoffset = blockoffset;	// which block overall. 
			dbpointer->ispresent = 1;				// watch out: this should be initialized to 0 for all!
			dbpointer->value = 0;
			dbpointer->startbyte = startbyte;
			dbpointer->fp = fpcount;

			// now, we got the first line, maybe there are more... 
			// count the number of blocks in the db.
			num = 1;
			idx[0] = 0;		// first block is index 0. 
			while (fscanf_s(fp, "%d", idx + num) == 1) 
			{
				num++;

				// Check for too many indices.
				//if (num == MAXIDX8) 
				//{
				//	return -2;
				//}
			} // while

			// We stopped reading numbers. 
			dbpointer->numberofblocks = num;
			dbpointer->idx = new int[num];

			memcpy(dbpointer->idx, idx, num * sizeof(int));
		}
		else 
		{
			switch (stat) {
			case '+':
				singlevalue = WIN;
				break;
			case '=':
				singlevalue = DRAW;
				break;
			case '-':
				singlevalue = LOSS;
				break;
			default:
				return(0);
			}
			dbpointer = &cprsubdatabase[wm][wk][bm][bk][wmrank][bmrank][color];
			dbpointer->blockoffset = 0;
			dbpointer->firstblock = 0;
			dbpointer->idx = NULL;
			dbpointer->ispresent = 1;
			dbpointer->numberofblocks = 0;
			dbpointer->value = singlevalue;
			dbpointer->fp = fpcount;
		}
	}

	fclose(fp);

	return num+firstblock;
}


int PreloadDatabase()
{
	// preloads the entire db or until the cache is full.
	FILE *fp;
	int n;
	unsigned char *diskblock;
	int cachepointer = 0;
	int DTCcachepointer = 0;
	wchar_t dbname[PATH_MAX];
	int blockoffset = 0;
	int autoloadnum = 0;
	int uniqueid = 0;


	for(n=2;n<MAXPIECES;n++)
	{
		SetReaderDatabaseName(dbname, n, false, false);
		_wfopen_s(&fp, dbname,L"rb");
		if(fp == NULL)
			break;

		while(!feof(fp))
		{
			// get a memory address to write block to:
			diskblock = CacheBaseAddress + 1024*cachepointer;
			// and save it in the blockpointer array
			BlockPointer[cachepointer] = diskblock;
			// cacheindex array tells which block id is at a certain place in cache - must update it
			BlockInfo[cachepointer].uniqueid = cachepointer; //change to uniqueid
			// say what we're doing
			
			// this was weird: fread statement was at the end of this loop before july 30 2007 - 
			// meaning that the last block of the cache was never read into memory?
			fread(diskblock,1024,1,fp);

			cachepointer++;

			// if we have preloaded the entire cache. no use going on.
			if(cachepointer >= CacheSize)
				return 1; 	
		} // while

		fclose(fp);
		// if we get to the autoload limit, we remember the block number up to which 
		// blocks will remain permanently in memory.

		SetReaderDatabaseName(dbname, n, false, true);
		_wfopen_s(&fp, dbname,L"rb");
		if(fp == NULL)
			break;

		while(!feof(fp))
		{
			// get a memory address to write block to:
			diskblock = DTCCacheBaseAddress + 1024*DTCcachepointer;
			// and save it in the blockpointer array
			DTCBlockPointer[DTCcachepointer] = diskblock;
			// cacheindex array tells which block id is at a certain place in cache - must update it
			DTCBlockInfo[DTCcachepointer].uniqueid = DTCcachepointer; //change to uniqueid
			// say what we're doing
			
			// this was weird: fread statement was at the end of this loop before july 30 2007 - 
			// meaning that the last block of the cache was never read into memory?
			fread(diskblock,1024,1,fp);

			DTCcachepointer++;

			// if we have preloaded the entire cache. no use going on.
			if(DTCcachepointer >= CacheSize)
				return 1; 	
		} // while

		fclose(fp);


		// if we get to the autoload limit, we remember the block number up to which 
		// blocks will remain permanently in memory.
		if(n==AUTOLOADSIZE)
			autoloadnum = cachepointer;

	} // for

	return autoloadnum;
}


int ReaderDbInit(int suggestedMB)
{
	FILE *fp;
	wchar_t dbname[PATH_MAX];
	int i,n;
	int singlevalue=0;
	int blockoffset = 0;
	int dtcblockoffset = 0;
	int autoloadnum = 0;
	int fpcount = 0;
	int pifreturnvalue;
	int pieces=0;
	uint32 memsize;

	InitBool();
	InitBinomCoef();

	// initialize runlength and lookup array 
	for(i=0;i<81;i++)
		RunLength[i]=4;

	for(i=81;i<256;i++)
	{
		RunLength[i]= skip[(i-81)%SKIPS];
		Value[i]= ((i-81)/SKIPS);
	}

	// set the ispresent flag to zero for all databases, because it later gets set to 1, and
	// it is not quite clear that this will not accidentally be one!
	memset(cprsubdatabase, 0, (MAXPIECE+1)*(MAXPIECE+1)*(MAXPIECE+1)*(MAXPIECE+1)*9*9*2*sizeof(cprsubdb));
	memset(dtcsubdatabase, 0, (MAXPIECE+1)*(MAXPIECE+1)*(MAXPIECE+1)*(MAXPIECE+1)*9*9*2*sizeof(dtcdb));
	
	// detect largest present database and put the number of pieces in variable pieces:
	for(n=2; n<MAXPIECES; n++)
	{
		SetReaderDatabaseName(dbname, n, true, false);
		_wfopen_s(&fp, dbname, L"rb");
		if(fp)
		{
			pieces=n;
			fclose(fp);
		}
		else
			break;
    }

	// what the hell is this good for
	// assign values according to pieces"
	switch(pieces)
	{
		case 2:
			;
		case 3:
			;
		case 4:
			maxidx = MAXIDX4;
			maxblocknum = BLOCKNUM4;
			maxpieces = 4;
			maxpiece = 4;
			break;
		case 5:
			maxidx = MAXIDX5;
			maxblocknum = BLOCKNUM5;
			maxpieces = 5;
			maxpiece = 5;
			break;
		case 6:
			maxidx = MAXIDX6;
			maxblocknum = BLOCKNUM6;
			maxpieces = 6;
			maxpiece = 6;
			break;
		case 7:
			maxidx = MAXIDX7;
			maxblocknum = BLOCKNUM7;
			maxpieces = 7;
			maxpiece = 4;
			break;
		case 8:
			maxidx = MAXIDX8;
			maxblocknum = BLOCKNUM8;
			maxpieces = 8;
			maxpiece = 4;
			break;
	}
	
	// get the number of blocks in cache
	CacheSize = 1024*( suggestedMB / 2);

	// parse index files
	// blockoffset is the total number of blocks in all dbs belonging to an index file.
	blockoffset = 0;

	for(n=2;n<=maxpieces;n++)
	{
		SetReaderDatabaseName(dbname, n, true, false);

		// parse next index file
		pifreturnvalue = ParseIndexFile(dbname,blockoffset,fpcount);
		
		// if the index file is not present, or if an error occurs during parsing,
		// we do nothing. else, we update the max number of pieces.
		if(pifreturnvalue >= 0)
		{
			blockoffset += pifreturnvalue;
		}

		SetReaderDatabaseName(dbname, n, true, true);

		pifreturnvalue = ParseDtcIndexFile(dbname, dtcblockoffset, fpcount);
		if(pifreturnvalue >= 0)
		{
			dtcblockoffset += pifreturnvalue;
			fpcount++;
		}
	}
	
	// allocate memory for the cache
	memsize = CacheSize*1024;
	CacheBaseAddress = new unsigned char[memsize];
	DTCCacheBaseAddress = new unsigned char[memsize];

	
	// allocate memory for blockpointers
	// statement below was sizeof(int) which returned 4 even on the 64-bit version of windows?!
	memsize = maxblocknum*sizeof(BlockPointer);
	BlockPointer = (unsigned char**) new unsigned char [memsize];
	DTCBlockPointer = (unsigned char**) new unsigned char [memsize];

	// set blockpointers to NULL - this statement crashes on 64-bit machine
	for(i=0;i<maxblocknum;i++)
	{
		BlockPointer[i]=NULL;
		DTCBlockPointer[i]=NULL;
	}
	
	// allocate memory for doubly linked list LRU
	memsize = CacheSize*sizeof(struct bi);
	BlockInfo =(bi*) new unsigned char[memsize];
	DTCBlockInfo =(bi*) new unsigned char[memsize];

	autoloadnum = PreloadDatabase();


	// prepare arrays which describe linked list
	// todo: if we want to autoload all blocks with a blocknumber < X (which preload would return)
	// then we have to go from for i=0 to for i=X 
	autoloadnum=0;
	for(i=autoloadnum;i<CacheSize;i++)  //0
	{
		BlockInfo[i].next = i+1;
		BlockInfo[i].prev = i-1;
		BlockInfo[i].uniqueid = i;
		DTCBlockInfo[i].next = i+1;
		DTCBlockInfo[i].prev = i-1;
		DTCBlockInfo[i].uniqueid = i;
	}
	BlockInfo[CacheSize-1].next=-1;
	BlockInfo[autoloadnum].prev=-1; //0
	DTCBlockInfo[CacheSize-1].next=-1;
	DTCBlockInfo[autoloadnum].prev=-1; //0
	
	Head=autoloadnum; //0
	Tail = CacheSize-1;
	DTCHead=autoloadnum; //0
	DTCTail = CacheSize-1;

	// open file pointers for each db: keep open all the time.
	fpcount=0;
	for(n=2; n<MAXPIECES; n++)
	{
		SetReaderDatabaseName(dbname, n, false, false);
		_wfopen_s(&(dbfp[fpcount]), dbname, L"rb");


		SetReaderDatabaseName(dbname, n, false, true);
		_wfopen_s(&(dtcdbfp[fpcount]), dbname, L"rb");

		fpcount++;
	}
	
	return maxpieces;
}

//-------------------------------------------
// initialization below
void ReaderDbExit()
{
	delete[] CacheBaseAddress;
	delete[] BlockPointer;		
	delete[] BlockInfo;

	delete[] DTCCacheBaseAddress;
	delete[] DTCBlockPointer;		
	delete[] DTCBlockInfo;

	for(int i=0;i<MAXFP;i++)
	{
		if(dbfp[i] != NULL)
			fclose(dbfp[i]);	
	}
}



}